package gui;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.LinkedList;


public class PegaNomesDosParametrosDeUmMetodo 
{
	/*se o metodo nao tem parametros, uma linkedlist vazia eh criada e se os parametros nao 
	 * conseguiram ser pegados mas o metodo tem parametros, uma excecao eh lancada*/
	public LinkedList<String> pegarNomesDosParametrosDeUmMetodo(Method metodo, String urlParaArquivoJavaQueTemOMetodo) throws NomesDosParametrosNotFoundException
	{
		LinkedList<String> nomesDosParametrosDeUmMetodo = new LinkedList<String>();
		
		if(metodo.getParameterTypes().length == 0)
		{
			//o metodo nao tem parametros de entrada, vamos retornar uma lista vazia
			return nomesDosParametrosDeUmMetodo;
		}
		else
		{
			String parametrosDeEntradaDoMetodoESeusNomes = encontrarParametrosDeEntradaDoMetodoESeusNomes(urlParaArquivoJavaQueTemOMetodo, metodo);
			if(parametrosDeEntradaDoMetodoESeusNomes != null)
			{
				//achamos a declaracao do metodo, mas precisamos somente dos nomes dos parametros
				String[] arranjoNomesDosParametrosDeUmMetodo = 
						this.pegarNomesParametrosDeEntrada
								(parametrosDeEntradaDoMetodoESeusNomes, metodo);
				for(int i = 0; i < arranjoNomesDosParametrosDeUmMetodo.length; i++)
				{
					nomesDosParametrosDeUmMetodo.add(arranjoNomesDosParametrosDeUmMetodo[i]);
				}
				
				return nomesDosParametrosDeUmMetodo;
			}
			else
			{
				System.out.println("///////////LINHA COM A DECLARACAO DO METODO NAO ENCONTRADA/////////");
				throw new NomesDosParametrosNotFoundException();
			}
		}
	}
	
	/*onde eu posso achar uma linha do arquivo com meu metodo?Pode ser o construtor do metodo
	 *  ou pode ser alguma funcao que chama o metodo.
	 *Enfim, a funcao abaixo vai tentar achar dentro do arquivo .java que tem um metodo, todos os tipos e nomes
	 *de todos os parametros de entrada do metodo(ex: para um metodo "int somar(int x, int y)" eu terei "int x, int y").
	 *Agora essa declaracao do metodo pode nao estar em uma linha unica do .java, ela pode estar dividida em varias linhas.
	 *O metodo passado como entrada tem de ter parametros de entrada */
	private String encontrarParametrosDeEntradaDoMetodoESeusNomes(String urlParaArquivoJavaQueTemOMetodo, Method metodo)
	{
		String nomeDoMetodo = metodo.getName();
		
		try
		{
			FileInputStream leitor = new FileInputStream(urlParaArquivoJavaQueTemOMetodo);
			Reader reader = new InputStreamReader(leitor, "UTF-8");
			BufferedReader buff = new BufferedReader(reader);
			String linhaDoArquivo = buff.readLine();
			
			while(linhaDoArquivo != null)
			{
				//enquanto ainda houver linhas do arquivo .java para serem lidas
				if(linhaDoArquivo.contains(nomeDoMetodo))
				{
					//a linha lida pelo menos cita o metodo que procuramos,pode ser que ela seja a declaracao do metodo. Vamos checar!
					//agora a declaracao do metodo pode comecar numa linha e terminar em outras.
					//primeiro vamos checar se logo apos o nome do metodo, temos um "(", mas pode ser um atributo com o nome do metodo tb. Checaremos se existe um ; ou ( apos o nome do metodo
					boolean descobriuSeEhAtributoOuMetodo = false;
					
					int ondeComecaONomeDoMetodoNaLinha = linhaDoArquivo.indexOf(nomeDoMetodo);
					boolean podeSerDeclaracaoDeMetodo = false;
					
					while(descobriuSeEhAtributoOuMetodo == false && linhaDoArquivo != null)
					{
						for(int i = ondeComecaONomeDoMetodoNaLinha; i < linhaDoArquivo.length(); i++)
						{
							char c = linhaDoArquivo.charAt(i);
							String caractere = String.valueOf(c);
							if(caractere.compareTo(";") == 0)
							{
								//achamos um atributo. So nao pode ser a declaracao de um metodo.
								//declaracoes nao pode ter: nomedometodo;(int i)
								descobriuSeEhAtributoOuMetodo = true;
								podeSerDeclaracaoDeMetodo = false;
								break;
							}
							else if(caractere.compareTo("(") == 0)
							{
								descobriuSeEhAtributoOuMetodo = true;
								podeSerDeclaracaoDeMetodo = true;
								break;
							}
						}
						
						if(descobriuSeEhAtributoOuMetodo == false)
						{
							//vamos ler mais linhas
							linhaDoArquivo = buff.readLine();
							ondeComecaONomeDoMetodoNaLinha = 0; //so pq o nome do metodo estava na linha anterior
						}
					}
					
					if(podeSerDeclaracaoDeMetodo == true)
					{
						//fase 2: achamos um "(", certo? Vamos ler o documento ate achar onde esse parenteses fecha. Pode estar em outras linhas
						boolean chegouAoFinalDoParenteses = false;
						String tudoQueEstaDentroDoParenteses = "";
						
						int indiceDeInicioParaProcuraDoFechaParenteses =
								linhaDoArquivo.indexOf("(") + 1; //eu quero comecar apos o parenteses
						
						while(chegouAoFinalDoParenteses == false && linhaDoArquivo != null)
						{
							for(int j = indiceDeInicioParaProcuraDoFechaParenteses; j < linhaDoArquivo.length(); j++)
							{
								char c = linhaDoArquivo.charAt(j);
								String caractere = String.valueOf(c);
								
								if(caractere.compareTo(")") == 0)
								{
									chegouAoFinalDoParenteses = true;
									break;
								}
								
								tudoQueEstaDentroDoParenteses = tudoQueEstaDentroDoParenteses + caractere;
							}
							
							if(chegouAoFinalDoParenteses == false)
							{
								//ainda nao achamos o final do parenteses, ele pode estar em outras linhas
								linhaDoArquivo = buff.readLine();
								indiceDeInicioParaProcuraDoFechaParenteses = 0; //comecaremos a procurar do inicio da linha
								
							}
						}
						
						if(chegouAoFinalDoParenteses == true)
						{
							//o parenteses foi fechado entao ainda ha esperanca de achar os nomes dos atributos do metodo
							LinkedList<String> tiposDosParametrosDoMetodo = this.pegarTiposDosParametrosDoMetodo(metodo);
							int quantosTiposDeParametrosAchados = 0;
							
							for(int i = 0; i < tiposDosParametrosDoMetodo.size(); i++)
							{
								String umTipo = tiposDosParametrosDoMetodo.get(i);
								String tudoQueEstaDentroDoParentesesSemEspacos = tudoQueEstaDentroDoParenteses.replace(" ", "");
								if(tudoQueEstaDentroDoParenteses.contains(umTipo) == true ||
										tudoQueEstaDentroDoParentesesSemEspacos.contains(umTipo) == true)
								{
									quantosTiposDeParametrosAchados = quantosTiposDeParametrosAchados + 1;
								}
							}
							
							if(quantosTiposDeParametrosAchados == tiposDosParametrosDoMetodo.size())
							{
								//achamos finalmente a declaracao do metodo, pois dentro dos parentes ha os tipos que o metodo define
								return tudoQueEstaDentroDoParenteses;
							}
						}
						
					}
					
					
				}
				
				if(linhaDoArquivo != null)
				{
					linhaDoArquivo = buff.readLine();
				}
			}
			
			return null; //se dentro de tudo isso eu nao achar os parametros de entrada, retorno null
		}
		catch(FileNotFoundException e) 
		{
			System.out.println(e);
			return null;
		}
		catch(IOException exc)
		{
			System.out.println(exc);
			return null;
		}
	}
	
	
	/*
	 * O tipo de um parametro tambem vem com os pacotes associados(ex: java.lang.String).
	 *  Eh bom soh ter o tipo mesmo desses parametros entao vamos tirar os pacotes associados, por isso pegaremos so o simplesName dos metodos
	 */
	@SuppressWarnings("rawtypes")
	private LinkedList<String> pegarTiposDosParametrosDoMetodo(Method m)
	{
		Class[] tiposDosParametrosEPacotes = m.getParameterTypes();
		LinkedList<String> nomesDosTiposDosParametrosEPacotes = new LinkedList<String>();
		
		for(int i = 0; i < tiposDosParametrosEPacotes.length; i++)
		{
			Class umTipoDeUmParametro = tiposDosParametrosEPacotes[i];
			String nomeDesseTipo = umTipoDeUmParametro.getSimpleName();
			
			nomesDosTiposDosParametrosEPacotes.add(nomeDesseTipo);
		}
		
		return nomesDosTiposDosParametrosEPacotes;
	}
	
	private String[] pegarNomesParametrosDeEntrada(String parametrosDeEntradaDoMetodoESeusNomes, Method metodo)
	{
		String somenteNomesDosParametros = parametrosDeEntradaDoMetodoESeusNomes;
		LinkedList<String> tiposDosParametrosDoMetodo =
				pegarTiposDosParametrosDoMetodo(metodo);
		String nomesDosParametrosSemEspacosEmBranco =
				somenteNomesDosParametros.replaceAll("\\s","");
		//vou vasculhar cada um dos tipos dos parametros de entrada e retira-los da string passada como parametro
		for(int i = 0; i < tiposDosParametrosDoMetodo.size(); i++)
		{
			String umTipo = tiposDosParametrosDoMetodo.get(i);
			System.out.println(umTipo);
			nomesDosParametrosSemEspacosEmBranco  = nomesDosParametrosSemEspacosEmBranco.replace(umTipo, "");
		}
		
		

		
		String[] nomesParametrosDeEntrada = nomesDosParametrosSemEspacosEmBranco.split(",");
		
		return nomesParametrosDeEntrada;
	}

}
